var gamejs = require('gamejs');

/**
 * An object that models a projectile fired by a Sprite.
 * It can hit any targettable and hittable Sprite.
 *
 * A Sprite is targettable if it has Sprite.rect correctly set.
 * A Sprite is hittable if it has the method:
 *    - hit(hitrate): subtract hitrate health points from target life
 *
 * @param {Sprite} target A targettable and hittable Sprite
 * @param {Sprite} fired_by the Sprite that fired this projectile. This Sprite
 *                 must have a getWeaponCoordinates().
 * @param {Number} speed The absolute value of the projectile speed (in px/s)
 *                 Must be > 0. [default 100px/s].
 * @param {Number} power The damage done to the target on hit. Must be > 0.
 *                 [default 1]
 *
 * @throws {TypeError} If target doesn't have a method named getRect
 * @throws {TypeError} If target doesn't have a method named hit
 * @throws {TypeError} If fired_by doesn't have a method named
 *                     getWeaponCoordinates
 * @throws {TypeError} If speed isn't a number or is NaN
 * @throws {TypeError} If power isn't a number or is NaN
 * @throws {RangeError} If speed is <= 0
 * @throws {RangeError} If power is <= 0
 */
var Projectile = function(target, fired_by, power, speed) {
	Projectile.superConstructor.apply(this, arguments);
	// Set target
	if ( ! target.rect ) {
		throw new TypeError("Projectile target must be targettable!");
	}
	if ( typeof target.hit !== 'function' ) {
		throw new TypeError("Projectile target must be hittable!");
	}
	this.target = target;

	// Set fired_by
	this.fired_by = fired_by;
	if ( typeof fired_by.getWeaponCoordinates !== 'function' ) {
		throw new TypeError("Projectile fired_by must have a " +
			"getWeaponCoordinates() method!");
	}

	// Set power
	if (arguments.length < 3) {
		// Use default value if power is not specified
		this.power = Projectile.DEFAULT_POWER;
	}
	else {
		if (typeof power !== "number" || power !== power) {
			throw new TypeError("Projectile power must be a number > 0");
		}
		if (power <= 0) {
			throw new RangeError("Projectile power must be a Number > 0");
		}
		this.power = power;
	}

	// Set speed
	if (arguments.length < 4) {
		// Use default value if speed is not specified
		this.speed = Projectile.DEFAULT_SPEED;
	}
	else {
		if (typeof speed !== "number" || speed !== speed) {
			throw new TypeError("Projectile speed must be a Number > 0");
		}
		if (speed <= 0) {
			throw new RangeError("Projectile speed must be a Number > 0");
		}
		this.speed = speed;
	}

	// Internal variables used to draw the ray
	this._tailPosition = fired_by.getWeaponCoordinates();
	var angle = this._calculateAngle();
	var headX = Math.cos(angle)*Projectile.BEAM_LENGTH + this._tailPosition[0];
	var headY = Math.sin(angle)*Projectile.BEAM_LENGTH + this._tailPosition[1];
	this._headPosition = [headX, headY];

};

// A Projectile it's a Sprite
gamejs.utils.objects.extend(Projectile, gamejs.sprite.Sprite);

// Export Projectile
exports.Projectile = Projectile;

Projectile.DEFAULT_SPEED = 300;
Projectile.DEFAULT_POWER = 1;
Projectile.BEAM_LENGTH = 15;

/**
 * Return the target of this Projectile
 *
 * @return {Object} The current target
 */
Projectile.prototype.getTarget = function() {
	return this.target;
};

/**
 * Set a new target for this Projectile
 *
 * @param {Object} new_target Must be a targettable and hittable object
 *
 * @throws {TypeError} If new_target doesn't have a method named getRect
 * @throws {TypeError} If new_target doesn't have a method named hit
 */
Projectile.prototype.setTarget = function(new_target) {
	if ( ! new_target.rect) {
		throw new TypeError("Projectile new_target must be targettable!");
	}
	if ( typeof new_target.hit !== 'function' ) {
		throw new TypeError("Projectile new_target must be hittable!");
	}
	this.target = new_target;
};

/**
 * Return the object that fired this projectile
 *
 * @return {Object}
 */
Projectile.prototype.getSource = function() {
	return this.fired_by;
};

/**
 * Return the projectile speed
 *
 * @return {Number} The absolute value of the projectile speed (in pixels/s)
 */
Projectile.prototype.getSpeed = function() {
	return this.speed;
};

/**
 * Return the projectile power (the damage done when hitting the target)
 *
 * @return {Number}
 */
Projectile.prototype.getPower = function() {
	return this.power;
};

/**
 * Overrides Sprite.update().
 */
Projectile.prototype.update = function(msDuration) {
	// Kill the projectile if it's target is already dead
	if ( this.target.isDead() ) {
		this.kill();
		return;
	}
	// Calculate the new position
	var pxCovered = this.speed * msDuration/1000;
	var angle = this._calculateAngle();
	var offsetX = Math.cos(angle) * pxCovered;
	var offsetY = Math.sin(angle) * pxCovered;
	this._headPosition = [
		this._headPosition[0] + offsetX,
		this._headPosition[1] + offsetY
	];
	this._tailPosition = [
		this._tailPosition[0] + offsetX,
		this._tailPosition[1] + offsetY
	];
	// If we reached the target we hit the target and kill the projectile
	if (this.target.rect.collideLine(this._headPosition, this._tailPosition)) {
		this.target.hit(this.power);
		this.kill();
	}

};

/**
 * Overrides Sprite.draw().
 */
Projectile.prototype.draw = function(surface) {
	gamejs.draw.line(
		surface, '#FA0064', this._headPosition, this._tailPosition, 2
	);
};

/**
 * Calculate the angle from the this._tailPosition to the target's center.
 */
Projectile.prototype._calculateAngle = function() {
	// Some variables used for calculations
	var targetPos = this.target.rect.center;
	var targetX = targetPos[0], targetY = targetPos[1];
	var beamX = this._tailPosition[0], beamY = this._tailPosition[1];
	// Translate targetX and targetY for atan2
	var tr_targetX = targetX - beamX;
	var tr_targetY = targetY - beamY;
	return Math.atan2(tr_targetY, tr_targetX);
};